#pragma once

#include <atlcoll.h>
#include <atlenc.h>
#include <atlutil.h>
#include "Logger.h"

namespace UTIL {

#ifdef __ATLSAFE_H__

/**
 * VARIANT from CComSafeArray.
 */
class CArrayVariant : public CComVariant
{
public:
	CArrayVariant(CComSafeArray<BSTR> &safeArray)
	{
		vt = VT_ARRAY | _ATL_AutomationType<BSTR>::type;
		parray = safeArray.Detach();
	}

	CArrayVariant(CComSafeArray<VARIANT> &safeArray)
	{
		vt = VT_ARRAY | _ATL_AutomationType<VARIANT>::type;
		parray = safeArray.Detach();
	}

	template<class T>
	CArrayVariant(CComSafeArray<T> &safeArray)
	{
		vt = VT_ARRAY | _ATL_AutomationType<T>::type;
		parray = safeArray.Detach();
	}
};

/**
* VARIANT from CStringA.
*/
class CPostDataVariant : public CComVariant
{
public:
	CPostDataVariant(const CStringA &request)
	{
		LPSAFEARRAY psa = SafeArrayCreateVector(VT_UI1, 0, request.GetLength());
		if (!psa) return;

		HRESULT hr;
		void* pData = NULL;
		if (FAILED(hr = SafeArrayAccessData(psa, &pData)))
			return;

		CopyMemory(pData, (LPCSTR)request, request.GetLength());
		if (FAILED(SafeArrayUnaccessData(psa)))
			return;

		vt = VT_ARRAY | VT_UI1;
		parray = psa;
	}
};

inline HRESULT CreateComEnumVARIANT(IUnknown** ppUnkEnum, VARIANT* begin, VARIANT* end, CComEnumFlags flags = AtlFlagCopy, IUnknown* pUnk = NULL)
{
    typedef CComObject<CComEnum<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT, _Copy<VARIANT>, CComMultiThreadModel> > EnumVar;

    HRESULT hr = S_OK;

    EnumVar* p = NULL;
    if (SUCCEEDED(hr = EnumVar::CreateInstance(&p)))
    {
        p->AddRef();

        if (S_OK == (hr = p->Init(begin, end, NULL, AtlFlagCopy)))
            hr = p->QueryInterface(IID_IUnknown, (void**)ppUnkEnum);

        p->Release();
    }

    return hr;
}

#endif//__ATLSAFE_H__

/**
 * Converts bool to VARIANT_BOOL.
 */
inline VARIANT_BOOL ToVariant(bool bVal)
{
	return bVal ? VARIANT_TRUE : VARIANT_FALSE;
}

/**
 * Loads string.
 */
inline CString GetString(UINT nID)
{
	CString value;
	value.LoadString(nID);
	return value;
}

/**
 * Loads string. If it`s unavailable pszDefault will be returned.
 */
inline CString GetString(UINT nID, LPCTSTR pszDefault)
{
	CString value;
	if (!value.LoadString(nID))
		return pszDefault;
	return value;
}

/**
 * Returns formatted string
 */
inline CString FormatString(UINT nFormatID, ...)
{
	CString strFormat;
	if (!strFormat.LoadString(nFormatID))
	{
		ATLASSERT(FALSE);
		return CString();
	}

	CString strResult;

	va_list argList;
	va_start( argList, nFormatID );
	strResult.FormatV( strFormat, argList );
	va_end( argList );

	return strResult;
}

/**
 * Returns formatted string
 */
inline CString FormatString(LPCTSTR pszFormatID, ...)
{
	CString strResult;

	va_list argList;
	va_start( argList, pszFormatID );
	strResult.FormatV( pszFormatID, argList );
	va_end( argList );

	return strResult;
}

#ifdef __ATLWIN_H__

/**
 * Returns formatted string
 */
inline CString GetWindowText(CWindow wnd)
{
	if (!wnd)
		return CString();

	CString strResult;
	wnd.GetWindowText(strResult);
	return strResult;
}

#endif//__ATLWIN_H__

#define GetWindowTitle() UTIL::GetWindowText(*this)
#define GetParentTitle() UTIL::GetWindowText(this->GetParent())

/**
 * Property set implementation.
 */
template<class I, class J>
inline HRESULT Copy(const CComPtr<J> &value, I** pVal)
{
	if (!pVal)
		return E_POINTER;

	if (*pVal = value)
		(*pVal)->AddRef();

	return S_OK;
}

/**
 * Property set implementation.
 */
inline HRESULT Copy(bool bValue, VARIANT_BOOL* pVal)
{
	if (!pVal)
		return E_POINTER;

	*pVal = ToVariant(bValue);

	return S_OK;
}

/**
 * Copy strings from CAtlArray to SAFEARRAY
 */
template<class T>
inline void CopyStrings(CAtlArray<T> &arResult, SAFEARRAY *pSafeArray)
{
	CComSafeArray<BSTR> arItems;
	arItems.Attach(pSafeArray);

	arResult.SetCount(arItems.GetCount());
	for (int i = 0; i < (int)arResult.GetCount(); ++i)
	{
		if (!arItems[i])
			continue;

		arResult[i] = arItems[i];
	}

	arItems.Detach();
}

#ifdef __ATLSAFE_H__

/**
* Copy strings from CAtlArray to SAFEARRAY
*/
template<class T, class G>
inline void CopyStrings(CComSafeArray<T> &arResult, const CAtlArray<G> &srcArray)
{
	arResult.Create(srcArray.GetCount());
	for (int i = 0; i < (int)srcArray.GetCount(); ++i)
		arResult[i] = srcArray[i];
}

#endif//__ATLSAFE_H__

/**
 * Move strings from one array to other.
 */
inline void MoveStrings(CAtlArray<CComBSTR> &arSource, CAtlArray<CComBSTR> &arDestination)
{
	arDestination.SetCount(arSource.GetCount());
	for (size_t i = 0; i < arSource.GetCount(); ++i)
		arDestination[i].Attach(arSource[i].Detach());
}

/**
 * Determines if URL is a file url.
 */
inline bool IsFileURL(LPCTSTR url)
{
	return ! _tcsnicmp(url, _T("file:///"), 8);
}

/**
 * Determines if URL is a file url.
 */
inline bool IsFileURL(const CComBSTR &url)
{
	return ! _wcsnicmp(url, L"file:///", 8);
}

/**
 * Determines if file is temporary.
 */
inline bool IsTempFile(LPCTSTR file)
{
	TCHAR path[MAX_PATH+1];
	ZeroMemory(path, sizeof(path));
	::GetTempPath(MAX_PATH, path);

	if (0 == _tcsnicmp(file, _T("file:///"), 8))
		file += 8;

	return 0 == _tcsnicmp(file, path, _tcslen(path));
}

/**
 * URL decoding.
 */
inline CString Decode(CString value)
{
	for (int idx; (idx = value.Find( _T('%') )) != -1; )
	{
		ATLASSERT(idx + 2 < value.GetLength());
		if (idx + 2 >= value.GetLength())
			break;

		DWORD dwCode;
		if (1 != _stscanf_s(value.Mid(idx+1, 2), _T("%x"), &dwCode))
		{ ATLASSERT(FALSE); break; }

		TCHAR szCharString[2] = { (TCHAR)dwCode, 0 };
		int nCount = value.Replace( value.Mid(idx, 3), szCharString );
		ATLASSERT(nCount > 0);
	}

	return value;
}

/**
 * URL to file.
 */
inline CString ToFile(CString url)
{
	//TODO check if this is url but not a file url
	if(!IsFileURL(url))
		return url;

	url = Decode(url);

	url.Delete(0, 8); // remove file:///
	url.Replace( _T('/'), _T('\\') );

	return url;
}

/**
 * File to URL.
 */
inline CString ToFileURL(CString file)
{
	if (IsFileURL(file))
		return file;

	file.Insert(0, _T("file:///"));
	file.Replace( _T('\\'), _T('/') );
	//TODO encode

	return file;
}


#ifdef __ATLCTRLS_H__

/**
 * Adds strings to ComboBox.
 *
 * combobox - combobox to add values
 * values	- array with values
 * nSelect	- index to SetCurSel
 */
template <class TArray>
inline void AddStrings(CComboBox &combobox, const TArray &values, int nSelect = -1)
{
	for (size_t i = 0; i < values.GetCount(); ++i)
	{
		int iPos = combobox.AddString(values[i]);
		combobox.SetItemData(iPos, i);
	}

	if (nSelect >= 0) combobox.SetCurSel(nSelect);
}

/**
 * Adds strings to ComboBoxEx.
 *
 * combobox - combobox to add values
 * values	- array with values
 * nSelect	- index to SetCurSel
 */
template <class TArray>
inline void AddStrings(CComboBoxEx &combobox, const TArray &values, int nSelect = -1)
{
	for (size_t i = 0; i < values.GetCount(); ++i)
		combobox.InsertItem(i, values[i], i, i, 0, i);

	if (nSelect >= 0) combobox.SetCurSel(nSelect);
}

#endif//__ATLCTRLS_H__

#ifdef __ATLSAFE_H__

/**
 * Creating CComSafeArray with URLs from CAtlArray with files.
 */
inline CComSafeArray<BSTR> &FilesToSafeUrls(const CAtlArray<CString> &files, CComSafeArray<BSTR> &safe_urls)
{
	safe_urls.Create(files.GetCount());
	for (int i = 0; i < (int)files.GetCount(); ++i)
	{
		if (files[i].IsEmpty())
			continue;

		if ( !_tcsnicmp(_T("file:///"), files[i], 8) || !_tcsnicmp(_T("http://"), files[i], 7) || !_tcsnicmp(_T("ftp://"), files[i], 6) )
			safe_urls[i] = files[i];
		else
			safe_urls[i] = ToFileURL(files[i]);
	}

	return safe_urls;
}

#endif//__ATLSAFE_H__

/**
 * Extracts File Name from path
 */
inline CString ExtractFileName(const CString &file)
{
	int idx = file.ReverseFind('\\');
	if (-1 == idx) idx = file.ReverseFind('/');
	return idx == -1 ? file : file.Mid(idx+1);
}

/**
* Extracts File Name from path without extension
*/
inline CString ExtractOnlyFileName(const CString &file)
{
	CString fileName = ExtractFileName(file);

	int idx = fileName.ReverseFind('.');
	if (-1 != idx) fileName.Delete(idx, fileName.GetLength() - idx);

	return fileName;
}

/**
 * Extracts File Extension from path
 */
inline CString ExtractFileExtension(const CString &file)
{
	int idx = file.ReverseFind('.');
	return idx == -1 ? CString() : file.Mid(idx+1).MakeLower();
}

/**
 * Returns temp file path without extension.
 * Max length is (MAX_PATH - 4).
 */
inline CString GetTempFilePath(const CString &strFile)
{
	TCHAR path[MAX_PATH];
	ZeroMemory(path, sizeof(path));
	::GetTempPath(MAX_PATH, path);

	int idx = strFile.ReverseFind( _T('/') );
	if (idx == -1)
		idx = strFile.ReverseFind( _T('\\') );

	if (idx == -1)
		_tcscat_s(path, _T("img"));
	else
	{
		int idx2 = strFile.ReverseFind( _T('.') );
		if (idx2 == -1 || idx2 < idx)
			_tcscat_s(path, strFile.Mid(idx+1));
		else
			_tcscat_s(path, strFile.Mid(idx+1, idx2-idx-1));
	}

	TCHAR bufTick[32] = _T("");
	_itot_s(::GetTickCount(), bufTick, 16);
	_tcscat_s(path, bufTick);

	// extension is mandatory
	if ( _tcslen(path) > MAX_PATH - 5 )
		path[MAX_PATH - 4] = 0;

	return path;
}

#ifdef __ATLAPP_H__

/**
 * Returns DLL path (with slash at the end of line).
 */
inline CString GetThisDLLPath()
{
	TCHAR path[MAX_PATH+1];
	ZeroMemory(path, sizeof(path));
	if (!GetModuleFileName(ModuleHelper::GetModuleInstance(), path, MAX_PATH))
		return CString();

	LPTSTR pszPos = _tcsrchr(path, _T('\\') );
	if (pszPos) *(pszPos+1) = 0;

	return path;
}
#endif __ATLAPP_H__

#ifdef _DEBUG
inline void TRACEREFCOUNT(IUnknown *pUnk)
{
	if (!pUnk)
		return;

	pUnk->AddRef();
	ULONG lCount = pUnk->Release();

#ifdef _USE_LOGGING
	CLogger logger("TRACEREFCOUNT");
	LOG( _T("RefCount: %i\n"), lCount );
#else
    ATLTRACE( _T("RefCount: %i\n"), lCount );
#endif
}
#else
#define TRACEREFCOUNT __noop
#endif // _DEBUG

inline DWORD GetOSMajorVersion()	// Vista or higher OS
{
	OSVERSIONINFO osvi;
	ZeroMemory(&osvi, sizeof(osvi));
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

	GetVersionEx(&osvi);

	return osvi.dwMajorVersion;
}

inline bool IsOSVistaOrHigher()	// Vista or higher OS
{
	static bool OS_VistaOrHigher = (GetOSMajorVersion() > 5);		// Vista or higher OS

	return OS_VistaOrHigher;
}

inline bool IsAttachedToIE()
{
	TCHAR path[MAX_PATH+1];
	ZeroMemory(path, sizeof(path));
	if (!GetModuleFileName(NULL, path, MAX_PATH))
		return true;

#ifdef _USE_LOGGING
	CLogger logger("MAIN");
	LOG("Attached to ", path);
#endif

	LPCTSTR p = _tcsrchr(path, _T('\\'));
	return p ? !_tcsicmp( _T("iexplore.exe"), p+1 ) : true;
}

inline bool IsIE()
{
	static bool s_bAttachedToIE = IsAttachedToIE();

	return s_bAttachedToIE;
}

bool IsAttachedToIE7OrHigher();

inline bool IsIE7OrHigher()
{
	static bool s_bAttachedToIE7OrHigher = IsAttachedToIE7OrHigher();

	return s_bAttachedToIE7OrHigher;
}

inline BOOL IsWow64()
{
	typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);

	LPFN_ISWOW64PROCESS fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")),"IsWow64Process");

	if (!fnIsWow64Process)
		return FALSE;

	BOOL bIsWow64 = FALSE;

	if (!fnIsWow64Process(GetCurrentProcess(), &bIsWow64))
		return FALSE;

	return bIsWow64;
}

// get dll version
CString GetVersion();

// UTF-8

inline CStringA EncodeToUTF8(const CStringW &strSource)
{
	CStringA strResult;

	int nCnt = AtlUnicodeToUTF8(strSource, strSource.GetLength(), NULL, 0);
	if (nCnt != 0)
	{
		nCnt = AtlUnicodeToUTF8(strSource, strSource.GetLength(), strResult.GetBuffer(nCnt), nCnt+1);
		strResult.ReleaseBuffer(nCnt);
	}

	return strResult;
}

CStringW DecodeFromUTF8(const CStringA &strSource);

inline  CStringA EncodeToUTF8(const CStringA &strSource)
{
	return EncodeToUTF8((CStringW)strSource);
}

inline  CStringA EncodeToUTF8(LPCTSTR pszSource)
{
	return EncodeToUTF8((CStringW)pszSource);
}

inline  CStringW DecodeFromUTF8(const CStringW &strSource)
{
	return DecodeFromUTF8((CStringA)strSource);
}

// BASE-64

CStringA EncodeToBASE64(const BYTE *pSource, int iLength);
CAtlArray<BYTE> &DecodeBASE64(const CStringA &strSource, CAtlArray<BYTE> &arResult);

inline  CStringA EncodeToBASE64(const CAtlArray<BYTE> &arSource)
{
	return EncodeToBASE64(arSource.GetData(), arSource.GetCount());
}

inline  CStringA EncodeToBASE64(const CStringA &strSource)
{
	return EncodeToBASE64((const BYTE*)(LPCSTR)strSource, strSource.GetLength());
}

inline  CStringA EncodeToBASE64UTF8(const CStringW &strSource)
{
	return EncodeToBASE64(EncodeToUTF8(strSource));
}

inline  CStringA EncodeToBASE64UTF8(const CStringA &strSource)
{
	return EncodeToBASE64UTF8((CStringW)strSource);
}

inline CString EncodeURL(const CString &strSource)
{
	TCHAR szEncodedURL[ATL_URL_MAX_URL_LENGTH];
	ZeroMemory(szEncodedURL, sizeof(szEncodedURL));

	DWORD dwEncodedFileLength = 0;
	AtlEscapeUrl(strSource, szEncodedURL, &dwEncodedFileLength, ATL_URL_MAX_PATH_LENGTH-1);

	return szEncodedURL;
}

BOOL CreateLowProcess(ProcessEx &process, LPCTSTR pszApplicationName, LPTSTR pszCommandLine = NULL);
BOOL CreateLowProcess(LPCTSTR pszApplicationName, LPTSTR pszCommandLine = NULL);
HRESULT SetThreadIntegrityLevelLow();
void ShowProcessIntegrityLevel();
void ShowThreadIntegrityLevel();

}; //namespace UTIL

#define RETURN_FAILED(expr)          { HRESULT hr = (expr); if (FAILED(hr)) return hr; }
#define RETURN_E_FAIL_ON_FALSE(expr) { if (!(expr)) return E_FAIL; }
#define RETURN_HRESULT(expr)         return (expr) ? S_OK : E_FAIL